import math

import funtest
from funtest import *
from funtest import MSG_IDIOMS
from funtest import quoted_value

import funtest_tests_aux

"""Tests for funtests module

This module provides funtests module testing and should been called
from console without any arguments.
You should see an example of funtest module work, it looks just
like unittest.
"""

#-------------------------------------------------------------------------
#                      Auxillary routines.
def check_num_calls(callables, numcalls_expected):
    """Check a number of calls.
    
    Check if callables in callables sequence
    were called the specified number of times.
    Each callable should have a "num_calls()" method.
    AssertionError raises if the condition is not met.
    """
    for item in callables:
        assert item.num_calls() == numcalls_expected, \
                ('the number of calls for ' + str(item) +
                 ' is ' + str(item.num_calls()))

#-------------------------------------------------------------------------
#                       Callable classes for testing.
class CallableSampleBase:
    
    """A base class for any sample callable used for testing.
    
    This class counts total number of calls occured.
    """
    
    def __init__(self):
        """Constructor."""
        self.__numcalls = 0
    
    def __call__(self, *args, **kwds):
        """Key method - is invoked in a call."""
        self.__numcalls += 1
        return self.on_call(*args, **kwds)
    
    def num_calls(self):
        """Return total number of calls."""
        return self.__numcalls
    
    def on_call(self):
        """On call actions.
        
        Override this method in child classes to add 
        the desired behaviour to callable instance call.
        """
        pass

class SampleRoutine0(CallableSampleBase):
    
    """A callable without any args or result."""
    
    pass

class SampleRoutine1(CallableSampleBase):
    
    """A callable with one arg and a boolean result True."""
    
    def on_call(self, arg):
        return True

class SampleRoutine2(CallableSampleBase):
    
    """A callable with two args and a result."""
    
    def on_call(self, arg1, arg2):
        return str(arg1) + '+' + str(arg2) + '=' + str(arg1 + arg2)

class SampleRoutine3(CallableSampleBase):
    
    """A callable with three args and a result."""
    
    def on_call(self, argA, argB, argC):
        return ('(' + str(argA) + '+' + str(argB) + ')*' +
                str(argC) + '=' + str((argA + argB) * argC))

class SampleRoutine3E(CallableSampleBase):
    
    """A callable with three args, call raises RuntimeError."""
    
    def on_call(self, argA, argB, argC):
        raise RuntimeError('RuntimeError example')

#-------------------------------------------------------------------------
#            Test routines - each routine is a separate test case.
def test_shortDescription_nodoc_noarg():
    """Test the TestCase description string.
    
    Sample function has no arguments
    and no docstring.
    """
    def nodoc_noarg_sample_function():
        pass
    testcase = funtest.TestCase(nodoc_noarg_sample_function, (), {})
    assert testcase.shortDescription() == (
            'nodoc_noarg_sample_function' +
            '\n' + MSG_IDIOMS['in_module'] + '__main__' +
            '\n' + MSG_IDIOMS['called_with'] +
            '\n' + MSG_IDIOMS['no_args'])

def test_shortDescription_noarg():
    """Test the TestCase description string.
    
    Sample function has no arguments
    and has a docstring.
    """
    def noarg_sample_function():
        """Sample function.
        docstring 2-nd line;
        docstring 3-rd line;
        """
        pass
    testcase = funtest.TestCase(noarg_sample_function, (), {})
    assert testcase.shortDescription() == (
            'noarg_sample_function' +
            '\n' + MSG_IDIOMS['in_module'] + '__main__' +
            '\n' + MSG_IDIOMS['called_with'] +
            '\n' + MSG_IDIOMS['no_args'])

def test_shortDescription():
    """Test the TestCase description string.
    
    Sample function has arguments
    and a docstring.
    """
    def the_sample_function(argument1, argument2):
        """Some sample function."""
        pass
    testcase = funtest.TestCase(the_sample_function,
                                (34, (1, 2, 3, 'Z')), {})
    assert testcase.shortDescription() == (
            'the_sample_function' +
            '\n' + MSG_IDIOMS['in_module'] + '__main__' +
            '\n' + MSG_IDIOMS['called_with'] +
            '\n' + quoted_value('34') +
            '\n' + quoted_value((1, 2, 3, 'Z')))

def test_shortDescription_KW():
    """Test the TestCase description string;
    
    Sample function arguments are given in a keyword
    syntax.
    """
    def the_sample_function():
        pass
    testcase = funtest.TestCase(the_sample_function, (),
                    {'argC':1, 'argA':5, 'argB':8})
    assert testcase.shortDescription() == (
            'the_sample_function' +
            '\n' + MSG_IDIOMS['in_module'] + '__main__' +
            '\n' + MSG_IDIOMS['called_with'] +
            '\n' + 'argA=' + quoted_value('5') +
            '\n' + 'argB=' + quoted_value('8') +
            '\n' + 'argC=' + quoted_value('1'))

def test_shortDescription_M():
    """Test the TestCase description string.
    
    Use mixed-style (both position and keyword arguments).
    """
    def the_sample_function():
        pass
    testcase = funtest.TestCase(the_sample_function, ('A1', 'B2'),
                                {'argC':1, 'argA':5, 'argB':8})
    assert testcase.shortDescription() == (
            'the_sample_function' +
            '\n' + MSG_IDIOMS['in_module'] + '__main__' +
            '\n' + MSG_IDIOMS['called_with'] +
            '\n' + quoted_value('A1') +
            '\n' + quoted_value('B2') +
            '\n' + 'argA=' + quoted_value('5') +
            '\n' + 'argB=' + quoted_value('8') +
            '\n' + 'argC=' + quoted_value('1'))

def test_shortDescription_modulename():
    """Test the TestCase description string.
    
    The sample function is defined in other module.
    """
    testcase = funtest.TestCase(funtest_tests_aux.aux_sample_function,
                                ('1', '2'), {})
    assert testcase.shortDescription() == (
            'aux_sample_function' +
            '\n' + MSG_IDIOMS['in_module'] + 'funtest_tests_aux' +
            '\n' + MSG_IDIOMS['called_with'] +
            '\n' + quoted_value('1') +
            '\n' + quoted_value('2'))

#-------------------------------------------------------------------------
#                   Main routine - module front-end.
def main():
    """Main routine - performs testing."""
    # Add tests for TestCase.shortDescription() method.
    add_OK_test(test_shortDescription_nodoc_noarg, None)
    add_OK_test(test_shortDescription_noarg, None)
    add_OK_test(test_shortDescription, None)
    add_OK_test(test_shortDescription_KW, None)
    add_OK_test(test_shortDescription_M, None)
    add_OK_test(test_shortDescription_modulename, None)
    # Create simple callable and tests for it.
    c0 = SampleRoutine0()
    add_OK_test(c0, None)
    # Create callable with one argument and tests
    # for it.
    c1 = SampleRoutine1()
    add_OK_test(c1, True, 10)
    # Create callable with two arguments and tests
    # for it.
    c2 = SampleRoutine2()
    add_OK_test(c2, '1+2=3', 1, 2)
    # Create callable with three arguments and tests
    # for it.
    c3 = SampleRoutine3()
    add_OK_test(c3, '(1+2)*4=12', 1, 2, 4)
    # Create callable with three arguments and tests
    # for it.  Use keyword arguments.
    c3K = SampleRoutine3()
    add_OK_test(c3K, '(1+2)*5=15', argA=1, argB=2, argC=5)
    # Create callable with three arguments and tests
    # for it.  Use mixed-style (both position and keyword arguments).
    c3M = SampleRoutine3()
    add_OK_test(c3M, '(1+2)*5=15', 1, argC=5, argB=2)
    # Create exception raising callable with three arguments and tests
    # for it.  Exception raise should been caught due to add_E_test()
    # routine use.
    c3EC = SampleRoutine3E()
    add_E_test(c3EC, RuntimeError, 2, 12, 85)
    # Create exception raising callable with three arguments and tests
    # for it.  Use keyword arguments.  Exception raise should 
    # been caught due to add_E_test() routine use.
    c3ECK = SampleRoutine3E()
    add_E_test(c3ECK, RuntimeError, argA=2, argB=12, argC=85)
    # Create exception raising callable with three arguments and tests
    # for it.  Use mixed-style (both position and keyword arguments).
    # Exception raise should been caught due to add_E_test() routine use.
    c3ECM = SampleRoutine3E()
    add_E_test(c3ECM, RuntimeError, 2, argC=85, argB=12)
    # Create a callable with  three arguments
    # and add a test suit for it "at once".
    c3S = SampleRoutine3()
    add_OK_suite(c3S,
                 ('(1+2)*5=15', 1, 2, 5),
                 ('(2+4)*10=60', 2, 4, 10),
                 ('(1+7)*3=24', 1, 7, 3))
    # Create error providing callable with  three arguments
    # and add a test suit for it "at once".
    c3ECS = SampleRoutine3()
    add_E_suite(c3ECS,
                (TypeError, 1, '2', 4),
                (TypeError, 2, (1, 2, 3), 10),
                (TypeError, 1, None, 3))
    # Check if callables were not called.
    check_num_calls((c0, c1, c2, c3,
                     c3K, c3M, c3EC,
                     c3ECK, c3ECM, c3S,
                     c3ECS), 0)
    # Perform tests added.
    print '\/' * 5 + 'call do_tests()' + '\/' * 5
    do_tests()
    print('<>' * 3 +"do_tests() finished" + '<>' * 3)
    # Check if callables were really called during tests.
    check_num_calls((c0, c1, c2, c3,
                     c3K, c3M, c3EC,
                     c3ECK, c3ECM), 1)
    check_num_calls((c3S, ), 3)
    check_num_calls((c3ECS, ), 3)
#-------------------------------------------------------------------------
#                  Call main routine if this module was run
#                  from console.
if __name__ == '__main__':
    main()
